﻿using System;
using System.Collections.Generic;
using JetBrains.Annotations;

namespace Hsenl {
    // 这些方法相比 GetComponent 之类的, 要快的多
    public static class BodiedExtension {
        private static readonly Stack<Entity> _cacheStack = new();
        private static readonly Queue<Entity> _cacheQueue = new();

        public static void ClearCache() {
            _cacheStack.Clear();
            _cacheQueue.Clear();
        }

        public static IBodied GetBodied(this Component component) {
            return component.Entity.Bodied;
        }

        public static IBodied GetTopBodied(this Component component) {
            var parent = component.Entity;
            _cacheStack.Clear();
            while (parent != null) {
                _cacheStack.Push(parent);
                parent = parent.Parent;
            }

            while (_cacheStack.Count > 0) {
                var e = _cacheStack.Pop();
                var bodied = e.Bodied;
                if (bodied != null)
                    return bodied;
            }

            return null;
        }

        public static T FindBodiedInParent<T>(this IBodied bodied) where T : class, IBodied {
            CheckBodied(bodied);
            var parent = bodied.Entity.Parent;
            while (parent != null) {
                if (parent.Bodied is T t)
                    return t;

                parent = parent.Parent;
            }

            return null;
        }

        public static T FindBodiedInRange<T>(this IBodied bodied) where T : class, IBodied {
            CheckBodied(bodied);
            // 广度优先
            var own = true;
            _cacheQueue.Clear();
            _cacheQueue.Enqueue(bodied.Entity);
            while (_cacheQueue.Count > 0) {
                var current = _cacheQueue.Dequeue();
                if (own) {
                    // 跳过自己
                    own = false;
                }
                else {
                    if (current.Bodied is T t)
                        return t;
                }

                var childCount = current.ChildCount;
                if (childCount != 0) {
                    for (var i = 0; i < childCount; i++) {
                        var child = current.GetChild(i);
                        _cacheQueue.Enqueue(child);
                    }
                }
            }

            return null;
        }

        public static T[] FindBodiedsInRange<T>(this IBodied bodied) where T : class, IBodied {
            CheckBodied(bodied);
            using var list = ListComponent<T>.Rent();
            bodied.FindBodiedsInRange(list);
            return list.ToArray();
        }

        public static void FindBodiedsInRange<T>(this IBodied bodied, List<T> results) where T : class, IBodied {
            CheckBodied(bodied);
            // 广度优先
            var own = true;
            _cacheQueue.Clear();
            _cacheQueue.Enqueue(bodied.Entity);
            while (_cacheQueue.Count > 0) {
                var current = _cacheQueue.Dequeue();
                if (own) {
                    // 跳过自己
                    own = false;
                }
                else {
                    if (current.Bodied is T t)
                        results.Add(t);
                }

                var childCount = current.ChildCount;
                if (childCount != 0) {
                    for (var i = 0; i < childCount; i++) {
                        var child = current.GetChild(i);
                        _cacheQueue.Enqueue(child);
                    }
                }
            }
        }

        public static T FindBodiedInTopRange<T>(this IBodied bodied) where T : class, IBodied {
            if (bodied is not Component component)
                throw new Exception($"Bodied must be a Component! '{bodied.GetType()}'");

            var mainBodied = component.GetTopBodied();
            return mainBodied?.FindBodiedInRange<T>();
        }

        private static void CheckBodied(IBodied bodied) {
            if (bodied is not Component)
                throw new Exception($"Bodied must be a Component! '{bodied.GetType()}'");
        }

        #region Component Extension

        public static T GetComponent<T>(this IBodied bodied) where T : class {
            return bodied.Entity.GetComponent<T>();
        }

        // public static void SetParent(this IBodied bodied, Entity parent) {
        //     bodied.Entity.SetParent(parent);
        // }
        //
        // public static void SetParent(this IBodied bodied, Entity parent, bool worldPositionStays) {
        //     bodied.Entity.SetParent(parent, worldPositionStays);
        // }

        #endregion
    }
}